local depth_buffer = require"base.depth_list"
local camera = require"library.camera"
local bump = require"library.bump.bump"
local Signal = oopsys.getcls("Signal")
local SerObject = oopsys.getcls("SerObject")

local world = class("World",Signal,SerObject){
    camera = nil,
    bump_world = nil,
    areas = {},
    objects = {},
    loading_center = {x = 0,y = 0},
    depth_buffer = nil,
}:reg()

for i,name in ipairs(LOVE_CALLBACK) do
    if world[name] == nil then
        world[name] = function(self,...)
            local bx,by = self.loading_center.x - 1,self.loading_center.y - 1
            for x = bx,bx + 2 do
                for y = by,by + 2 do
                    local area = self:get_area(x,y,true)
                    if area then
                        for _,object in pairs(area.objects) do
                            if object[name] then
                                object[name](object,...)
                            end
                        end
                    end
                end
            end
        end
    end
end

function world:__init__()
    self.camera = camera(0,0)
    self.bump_world = bump.newWorld(WORLD_AREA_PSIZE)
    self.depth_buffer = depth_buffer(64)

    self:signal("not_area")
    self:signal("add_object")
    self:signal("remove_object")
end

function world:world_position_to_area_position(x,y)
    return math.floor(x / WORLD_AREA_PSIZE),math.floor(y / WORLD_AREA_PSIZE)
end

function world:area_position_to_world_position(ax,ay)
    return ax * WORLD_AREA_PSIZE,ay * WORLD_AREA_PSIZE
end

--添加区块
function world:add_area(area)
    if not area then return self end
    
    local x,y = area.ix,area.iy
    if not self.areas[x] then
        self.areas[x] = {}
    end
    self.areas[x][y] = area
    --self.depth_buffer:index_insert(area,area,area.depth)
    return self
end

function world:_get_area(x,y)
    if not self.areas[x] then
        self.areas[x] = {}
    end
    return self.areas[x][y]
end

--获取区块  is_load:未加载时是否进行加载
function world:get_area(x,y,is_load)
    local area = self:_get_area(x,y)
    if is_load and (not area) then
        self:load_area(x,y)
        area = self:_get_area(x,y)
    end
    return area
end

function world:get_area_from_world_position(x,y,is_load)
    x,y = self:world_position_to_area_position(x,y)
    return self:get_area(x,y,is_load)
end

function world:set_loading_center(x,y)
    self.loading_center.x = x or self.loading_center.x
    self.loading_center.y = x or self.loading_center.y
end

function world:set_loading_center_from_world_poaition(x,y)
    x,y = self:world_position_to_area_position(x,y)
    self.loading_center.x = x or self.loading_center.x
    self.loading_center.y = y or self.loading_center.y
end

function world:load_area(x,y)
    
end

function world:unload_area(x,y)

end

function world:get_hover_area()
    local ax,ay = self:world_position_to_area_position(
        self.camera:toWorldCoords(love.mouse.getPosition())
    )
    return self:get_area(ax,ay)
end

function world:get_hover_grid()
    local area = self:get_hover_area()
    if area then
        return area:_get_mouse_grid()
    end
end

function world:add_object(object,name)
    name = name or object.name
    if self.objects[name] ~= nil then return end

    local x,y = self:world_position_to_area_position(object.x,object.y)
    local area = self:get_area(x,y,true)
    if area == nil then
        self:emit_signal("not_area",self,x,y)
        --在获取一次
        area = self:get_area_from_world_position(object.x,object.y,true)
    end
    if area then
        object.name = name
        self.objects[object.name] = object
        area:add(object)
        self.depth_buffer:index_insert(object,object,object.depth or 0)
    
        if oopsys.is(object,"BumpObject") then
            
        end
    end
    return self
end

function world:remove_object(name_or_object)
    if name_or_object == nil then return self end
    local obj = name_or_object
    if type(obj) ~= "table" then
        obj = self.objects[obj]
    end
    if obj == nil then return self end
    obj._area:remove(obj)
    self.objects[obj.name] = nil
    self.depth_buffer:index_remove(object)
    return self
end

function world:move_object(object,vx,vy)
    if vx + vy == 0 then return end
    local old_area = self:get_area_from_world_position(object.x,object.y)
    object.x = object.x + vx
    object.y = object.y + vy
    local new_area = self:get_area_from_world_position(object.x,object.y)
    if old_area ~= new_area then
        old_area:remove(object)

        if not new_area then
            self:emit_signal("not_area",self,self:world_position_to_area_position(object.x,object.y))
            new_area = self:get_area_from_world_position(object.x,object.y)
        end
        if new_area then
            new_area:add(object)
        end
    end 
end

function world:get_object(name)
    if not name then return nil end
    return self.objects[name]
end

function world:update(dt)
    self.camera:update(dt)
    local bx,by = self.loading_center.x - 1,self.loading_center.y - 1
    for x = bx,bx + 2 do
        for y = by,by + 2 do
            local area = self:get_area(x,y,true)
            if area then
                for name,object in pairs(area.objects) do
                    if object.update then
                        object:update(dt)
                        local dbufnode = self.depth_buffer.__node_index[object]
                        if object.draw and dbufnode then
                            dbufnode.depth = object.depth
                            self.depth_buffer:_update_node_depth(dbufnode)
                        end
                    end
                end
            end
        end
    end
end

local function _is_draw(self,obj)
    local oax,oay = self:world_position_to_area_position(obj.x,obj.y)
    local ax,ay = self.loading_center.x - 1,self.loading_center.y - 1
    return not (
        oax < ax or
        oay < ay or
        oax > ax + 2 or 
        oay > ay + 2
    )
end

function world:draw()
    if core.core_mode == CORE_MODE_CLIENT then
        self.camera:draw_begin()
        local bx,by = self.loading_center.x - 1,self.loading_center.y - 1
        for x = bx,bx + 2 do
            for y = by,by + 2 do
                local area = self:get_area(x,y,true)
                if area then
                    area:draw(self.camera)
                end
            end
        end

        for node in self.depth_buffer:items() do
            local obj = node.data
            if _is_draw(self,obj) then
                obj:draw(self.camera) 
            else
                self.depth_buffer:index_remove(node) 
            end
        end
        self.camera:draw_end()
        self.camera:draw()
    end
end

return world
